"""
A cache for Flickr authentication information, so that the user must only login
the first time an api key requests access to his account.

To avoid using authentication information caching, set use_auth_cache=False when
calling the .login() method of a Flickr object.

"""

import os

# module imports
from error import FlickrError, FlickrFailure, CacheError
from json import json_decode, json_encode

class AuthCache(dict):
    """
    Store Flickr auth info (including tokens) with user and permission info in
    a JSON formatted file.
    """
    def __init__(self, path=None):
        if not path:
            # TODO see what this does on windows
            path = os.path.expanduser(os.path.sep.join(["~", ".flickr","token_cache"]))
        self.path = path

        self.load()

    def cache_auth(self, api_key, auth):
        """
        Cache the auth info under the given key.

        api_key is the api_key used to retrieve this token
        auth is a dictionary of the auth information returned by
          flickr.auth.checkToken/getToken:

            auth = {
                'token': token,
                'perms': perms,
                'user' : {
                    'nsid' : nsid,
                    'username' : username,
                    'fullname' : fullname,
                },
            }

        The auth info is stored like:

            auth_cache[api_key][nsid][perms] = auth

        and saved to disk.

        """
        user = auth['user']
        nsid = user['nsid']
        perms = auth['perms']

        if not self.has_key(api_key):
            self[api_key] = {}

        if not self[api_key].has_key(nsid):
            self[api_key][nsid] = {}

        self[api_key][nsid][perms] = auth
        self.save()

    def load(self):
        """
        Load the cache from a file

        """
        if os.path.exists(self.path):
            f = open(self.path, 'r')
            r = json_decode(f.read())
            f.close()
        else:
            r = {}
        self.update(r)

    def save(self):
        """
        Save the cache to a file.

        """
        f = open(self.path, 'w')
        f.write(json_encode(self))
        f.close()

    def load_auth(self, api_key, nsid, minimum_perms, flickr_api=None):
        """
        Try to get the desired auth info from the cache.  If it doesn't exist,
        throw a CacheError.  The first matching auth information, with the highest
        permissions, will be returned.

        nsid may be 'None' but this will result in a CacheError if there is auth
        information for more than one nsid.

        The flickr_api object, if it is passed, is used to check the auth info
        for validity and deletes those that are no longer valid.  It should use
        the same api_key that was passed and have the proper shared_secret.

        """
        available_perms = ['read', 'write', 'delete']

        perms_to_try = available_perms[available_perms.index(minimum_perms):]

        if not self.has_key(api_key):
            raise CacheError, "api key not in cache"

        if not nsid:
            # nsid is not defined, so hopefully there is just one nsid used
            # with this api_key, otherwise it's ambiguous
            if len(self[api_key].keys()) == 1:
                nsid = self[api_key].keys()[0]
            else:
                raise CacheError, "no nsid specified, and more than one for this `api_key`"

        for perms in reversed(perms_to_try):
            try:
                auth = self[api_key][nsid][perms]
            except KeyError:
                continue

            if flickr_api:
                try:
                    flickr_api.auth.checkToken(auth_token=auth['token'])
                except FlickrFailure, e:
                    if e.code == 98: # Invalid token
                        del self[api_key][nsid][perms]
                        continue
                    else:
                        raise
            return auth
        else:
            raise CacheError, 'no valid auth info in cache'

